{
  "cells": [
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "77gENRVX40S7"
      },
      "source": [
        "##### Copyright 2019 The TensorFlow Authors."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "cellView": "form",
        "id": "d8jyt37T42Vf"
      },
      "outputs": [],
      "source": [
        "#@title Licensed under the Apache License, Version 2.0 (the \"License\");\n",
        "# you may not use this file except in compliance with the License.\n",
        "# You may obtain a copy of the License at\n",
        "#\n",
        "# https://www.apache.org/licenses/LICENSE-2.0\n",
        "#\n",
        "# Unless required by applicable law or agreed to in writing, software\n",
        "# distributed under the License is distributed on an \"AS IS\" BASIS,\n",
        "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n",
        "# See the License for the specific language governing permissions and\n",
        "# limitations under the License."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "cellView": "form",
        "id": "aPxHdjwW5P2j"
      },
      "outputs": [],
      "source": [
        "#@title MIT License\n",
        "#\n",
        "# Copyright (c) 2017 François Chollet                                                                                                                    # IGNORE_COPYRIGHT: cleared by OSS licensing\n",
        "#\n",
        "# Permission is hereby granted, free of charge, to any person obtaining a\n",
        "# copy of this software and associated documentation files (the \"Software\"),\n",
        "# to deal in the Software without restriction, including without limitation\n",
        "# the rights to use, copy, modify, merge, publish, distribute, sublicense,\n",
        "# and/or sell copies of the Software, and to permit persons to whom the\n",
        "# Software is furnished to do so, subject to the following conditions:\n",
        "#\n",
        "# The above copyright notice and this permission notice shall be included in\n",
        "# all copies or substantial portions of the Software.\n",
        "#\n",
        "# THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n",
        "# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n",
        "# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL\n",
        "# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n",
        "# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n",
        "# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n",
        "# DEALINGS IN THE SOFTWARE."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "hRTa3Ee15WsJ"
      },
      "source": [
        "# Перенос обучения и тонкая настройка"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "dQHMcypT3vDT"
      },
      "source": [
        "<table class=\"tfo-notebook-buttons\" align=\"left\">\n",
        "  <td>\n",
        "    <a target=\"_blank\" href=\"https://www.tensorflow.org/tutorials/images/transfer_learning\"><img src=\"https://www.tensorflow.org/images/tf_logo_32px.png\" />Смотреть на TensorFlow.org</a>\n",
        "  </td>\n",
        "  <td>\n",
        "    <a target=\"_blank\" href=\"https://colab.research.google.com/github/tensorflow/docs-l10n/blob/master/site/ru/tutorials/images/transfer_learning.ipynb\"><img src=\"https://www.tensorflow.org/images/colab_logo_32px.png\" />Запустить в Google Colab</a>\n",
        "  </td>\n",
        "  <td>\n",
        "    <a target=\"_blank\" href=\"https://github.com/tensorflow/docs-l10n/blob/master/site/ru/tutorials/images/transfer_learning.ipynb\"><img src=\"https://www.tensorflow.org/images/GitHub-Mark-32px.png\" />Смотреть исходные файлы на GitHub</a>\n",
        "  </td>\n",
        "  <td>\n",
        "    <a href=\"https://storage.googleapis.com/tensorflow_docs/docs-l10n/site/ru/tutorials/images/transfer_learning.ipynb\"><img src=\"https://www.tensorflow.org/images/download_logo_32px.png\" />Скачать ноутбук</a>\n",
        "  </td>\n",
        "</table>"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "728d80d02ce9"
      },
      "source": [
        "Note: Вся информация в этом разделе переведена с помощью русскоговорящего Tensorflow сообщества на общественных началах. Поскольку этот перевод не является официальным, мы не гарантируем что он на 100% аккуратен и соответствует [официальной документации на английском языке](https://www.tensorflow.org/?hl=en). Если у вас есть предложение как исправить этот перевод, мы будем очень рады увидеть pull request в [tensorflow/docs](https://github.com/tensorflow/docs) репозиторий GitHub. Если вы хотите помочь сделать документацию по Tensorflow лучше (сделать сам перевод или проверить перевод подготовленный кем-то другим), напишите нам на [docs-ru@tensorflow.org list](https://groups.google.com/a/tensorflow.org/forum/#!forum/docs-ru)."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "2X4KyhORdSeO"
      },
      "source": [
        "В этом руководстве вы узнаете, как классифицировать изображения кошек и собак с помощью передачи обучения из предварительно обученной модели.\n",
        "\n",
        "Предварительно обученная модель - это сохраненная сеть, которая ранее была обучена на большом наборе данных, обычно на крупномасштабной задаче классификации изображений. Вы можете либо использовать предварительно обученную модель как есть, либо использовать перенос обучения, и настроить вашу модель для данной задачи.\n",
        "\n",
        "Идея, лежащая в основе переноса обучения для классификации изображений, заключается в том, что если модель обучается на достаточно большом и достаточно общем наборе данных, то эта модель будет эффективно служить в качестве общей модели визуального мира. Таким образом вы можете воспользоваться преимуществами изученных структур признаков без необходимости начинать с нуля обучение большой модели на большом наборе данных.\n",
        "\n",
        "В этом ноутбуке вы попробуете два способа использования предварительно обученной модели:\n",
        " \n",
        "1. Извлечение признаков: используйте представления, полученные в предыдущей сети, для извлечения значимых признаков из новых образцов. Вы можете просто добавить новый классификатор, который будет обучаться с нуля, поверх предварительно обученной модели, чтобы вы могли перенастроить функции извлечения признаков, обученные ранее в перенесенной модели.\n",
        "\n",
        " Вам не нужно повторно тренировать всю модель. Базовая сверточная сеть уже содержит функции, которые обычно полезны для классификации изображений. Однако последняя часть предварительно обученной модели специфична для исходной задачи классификации, и как результат - для набора классов, на которых была обучена модель.\n",
        "\n",
        "1. Тонкая настройка: разморозьте несколько верхних слоев основной замороженной модели и совместно тренируйте их и новые добавленные слои классификатора. Это позволяет нам более «тонко настроить» представления функций высокого порядка в базовой модели, чтобы сделать их более подходящими для конкретной задачи.\n",
        "\n",
        "Вы будете следовать общему рабочему процессу машинного обучения.\n",
        "\n",
        "1. Изучение и понимание данных\n",
        "1. Создание конвейера ввода данных, в данном случае Keras ImageDataGenerator.\n",
        "1. Создание модели.\n",
        "   * Загрузка предварительно обученной базовой модели (и предварительно обученных весов)\n",
        "   * Добавление сверху ваших собственных слоев классификации\n",
        "1. Обучение модели\n",
        "1. Оценка модели\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "gjWM9Lkqm0C1"
      },
      "outputs": [],
      "source": [
        "!pip install tf-nightly"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "TqOt6Sv7AsMi"
      },
      "outputs": [],
      "source": [
        "import matplotlib.pyplot as plt\n",
        "import numpy as np\n",
        "import os\n",
        "import tensorflow as tf\n",
        "\n",
        "from tensorflow.keras.preprocessing import image_dataset_from_directory"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "v77rlkCKW0IJ"
      },
      "source": [
        "## Предобработка данных"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "0GoKGm1duzgk"
      },
      "source": [
        "### Загрузка данных"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "vHP9qMJxt2oz"
      },
      "source": [
        "В этом уроке вы будете использовать набор данных, содержащий несколько тысяч изображений кошек и собак. Загрузите и распакуйте zip-файл, содержащий изображения, затем создайте два tf.data.Dataset-а, для обучения и для проверки, с помощью утилиты tf.keras.preprocessing.image_dataset_from_directory. Вы можете узнать больше о загрузке изображений в этом [руководстве](https://www.tensorflow.org/tutorials/load_data/images)."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "ro4oYaEmxe4r"
      },
      "outputs": [],
      "source": [
        "_URL = 'https://storage.googleapis.com/mledu-datasets/cats_and_dogs_filtered.zip'\n",
        "path_to_zip = tf.keras.utils.get_file('cats_and_dogs.zip', origin=_URL, extract=True)\n",
        "PATH = os.path.join(os.path.dirname(path_to_zip), 'cats_and_dogs_filtered')\n",
        "\n",
        "train_dir = os.path.join(PATH, 'train')\n",
        "validation_dir = os.path.join(PATH, 'validation')\n",
        "\n",
        "BATCH_SIZE = 32\n",
        "IMG_SIZE = (160, 160)\n",
        "\n",
        "train_dataset = image_dataset_from_directory(train_dir,\n",
        "                                             shuffle=True,\n",
        "                                             batch_size=BATCH_SIZE,\n",
        "                                             image_size=IMG_SIZE)"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "cAvtLwi7_J__"
      },
      "outputs": [],
      "source": [
        "validation_dataset = image_dataset_from_directory(validation_dir,\n",
        "                                                  shuffle=True,\n",
        "                                                  batch_size=BATCH_SIZE,\n",
        "                                                  image_size=IMG_SIZE)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "yO1Q2JaW5sIy"
      },
      "source": [
        "Покажем первые 9 изображений из тренировочного датасета:"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "K5BeQyKThC_Y"
      },
      "outputs": [],
      "source": [
        "class_names = train_dataset.class_names\n",
        "\n",
        "plt.figure(figsize=(10, 10))\n",
        "for images, labels in train_dataset.take(1):\n",
        "  for i in range(9):\n",
        "    ax = plt.subplot(3, 3, i + 1)\n",
        "    plt.imshow(images[i].numpy().astype(\"uint8\"))\n",
        "    plt.title(class_names[labels[i]])\n",
        "    plt.axis(\"off\")"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "EZqCX_mpV3Mx"
      },
      "source": [
        "Поскольку исходный набор данных не содержит тестового датасета, вы создадите его. Для этого определите, сколько пакетов данных доступно в проверочном датасете, используя tf.data.experimental.cardinality, а затем переместите 20% из них в тестовый датасет."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "uFFIYrTFV9RO"
      },
      "outputs": [],
      "source": [
        "val_batches = tf.data.experimental.cardinality(validation_dataset)\n",
        "test_dataset = validation_dataset.take(val_batches // 5)\n",
        "validation_dataset = validation_dataset.skip(val_batches // 5)"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "Q9pFlFWgBKgH"
      },
      "outputs": [],
      "source": [
        "print('Количество проверочных пакетов: %d' % tf.data.experimental.cardinality(validation_dataset))\n",
        "print('Количество тестовых пакетов: %d' % tf.data.experimental.cardinality(test_dataset))"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "MakSrdd--RKg"
      },
      "source": [
        "### Конфигурирование датасета для повышения производительности"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "22XWC7yjkZu4"
      },
      "source": [
        "Используйте буферизованную предварительную выборку для загрузки изображений с диска без блокировки ввода-вывода. Чтобы узнать больше об этом методе, см. Руководство [производительность данных](https://www.tensorflow.org/guide/data_performance)."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "p3UUPdm86LNC"
      },
      "outputs": [],
      "source": [
        "AUTOTUNE = tf.data.experimental.AUTOTUNE\n",
        "\n",
        "train_dataset = train_dataset.prefetch(buffer_size=AUTOTUNE)\n",
        "validation_dataset = validation_dataset.prefetch(buffer_size=AUTOTUNE)\n",
        "test_dataset = test_dataset.prefetch(buffer_size=AUTOTUNE)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "MYfcVwYLiR98"
      },
      "source": [
        "### Аугментация данных"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "bDWc5Oad1daX"
      },
      "source": [
        "Если у вас нет большого набора данных изображений, рекомендуется искусственно создавать различные реалистичные образцы, применяя случайные преобразования к исходным изображениям, например поворот или горизонтальное отражение. Это предоставит модели различные варианты обучающих данных и уменьшит [переобучение] (https://www.tensorflow.org/tutorials/keras/overfit_and_underfit). Вы можете узнать больше об аугментации данных в этом [руководстве](https://www.tensorflow.org/tutorials/images/data_augmentation)."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "3P99QiMGit1A"
      },
      "outputs": [],
      "source": [
        "data_augmentation = tf.keras.Sequential([\n",
        "  tf.keras.layers.experimental.preprocessing.RandomFlip('horizontal'),\n",
        "  tf.keras.layers.experimental.preprocessing.RandomRotation(0.2),\n",
        "])"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "s9SlcbhrarOO"
      },
      "source": [
        "Примечание: эти слои активны только в процессе тренировки, когда вы вызываете `model.fit`. Они неактивны, когда модель используется в режиме оценки - `model.evaulate` или прогнозирования - ` model.predict`."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "9mD3rE2Lm7-d"
      },
      "source": [
        "Давайте несколько раз применим эти слои к одному и тому же изображению и посмотрим на результат."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "aQullOUHkm67"
      },
      "outputs": [],
      "source": [
        "for image, _ in train_dataset.take(1):\n",
        "  plt.figure(figsize=(10, 10))\n",
        "  first_image = image[0]\n",
        "  for i in range(9):\n",
        "    ax = plt.subplot(3, 3, i + 1)\n",
        "    augmented_image = data_augmentation(tf.expand_dims(first_image, 0))\n",
        "    plt.imshow(augmented_image[0] / 255)\n",
        "    plt.axis('off')"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "bAywKtuVn8uK"
      },
      "source": [
        "### Масштабирование значений пикселей\n",
        "\n",
        "Сейчас вы загрузите `tf.keras.applications.MobileNetV2` для использования в качестве базовой модели. Эта модель ожидает значений пикселей в диапазоне `[-1,1]`, но на данный момент значения пикселей в ваших изображениях находятся в диапазоне `[0-255]`. Чтобы изменить их масштаб, используйте имеющийся в модели метод предварительной обработки `preprocess_input`."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "cO0HM9JAQUFq"
      },
      "outputs": [],
      "source": [
        "preprocess_input = tf.keras.applications.mobilenet_v2.preprocess_input"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "xnr81qRMzcs5"
      },
      "source": [
        "Примечание. В качестве альтернативы вы можете изменять масштаб значений пикселей с `[0,255]` на `[-1, 1]`, используя слой [Rescaling](https://www.tensorflow.org/api_docs/python/tf/keras/layers/experimental/preprocessing/Rescaling)."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "R2NyJn4KQMux"
      },
      "outputs": [],
      "source": [
        "rescale = tf.keras.layers.experimental.preprocessing.Rescaling(1./127.5, offset= -1)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "Wz7qgImhTxw4"
      },
      "source": [
        "Примечание: при использовании других моделей из `tf.keras.applications` обязательно проверьте документацию API, чтобы определить, ожидают ли они пикселей в диапазоне `[-1,1]` или `[0,1]`, или используйте встроенную функцию `preprocess_input`."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "OkH-kazQecHB"
      },
      "source": [
        "## Создание базовой модели из предварительно обученной сверточной сети\n",
        "\n",
        "Вы создадите базовую модель на основе модели **MobileNet V2**, разработанной в Google. Эта модель предварительно обучена на ImageNet, большом наборе данных, состоящем из 1,4 млн изображений и 1000 классов. ImageNet - это набор данных для исследовательской подготовки с широким спектром категорий, таких как `jackfruit` и `syringe`. Эта база знаний поможет нам классифицировать кошек и собак по нашему конкретному набору данных.\n",
        "\n",
        "Во-первых, вам нужно выбрать, какой уровень MobileNet V2 вы будете использовать для извлечения признаков. Самый последний уровень классификации («верхний», поскольку большинство диаграмм моделей машинного обучения идут снизу вверх) не очень полезен. Вместо этого вы будете следовать общепринятой практике - полагаться на самый последний слой перед операцией flatten(разворачивание матрицы в одномерный массив). Этот слой называется «узким местом». Элементы слоя «узкое место» сохраняют большую универсальность по сравнению с последним/верхним слоем.\n",
        "\n",
        "Сначала создайте экземпляр модели MobileNet V2 с предварительно загруженными весами, обученными на ImageNet. Указав аргумент **include_top = False**, вы загружаете сеть, которая не включает верхние слои классификации, что идеально подходит для извлечения функций."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "19IQ2gqneqmS"
      },
      "outputs": [],
      "source": [
        "# Создание базовой модели из предварительно обученной модели MobileNet V2\n",
        "IMG_SHAPE = IMG_SIZE + (3,)\n",
        "base_model = tf.keras.applications.MobileNetV2(input_shape=IMG_SHAPE,\n",
        "                                               include_top=False,\n",
        "                                               weights='imagenet')"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "AqcsxoJIEVXZ"
      },
      "source": [
        "Эта базовая модель преобразует входное изображение из матрицы `160x160x3` в матрицу `5x5x1280`. Давайте посмотрим, что он делает с примером пакета изображений:"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "Y-2LJL0EEUcx"
      },
      "outputs": [],
      "source": [
        "image_batch, label_batch = next(iter(train_dataset))\n",
        "feature_batch = base_model(image_batch)\n",
        "print(feature_batch.shape)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "rlx56nQtfe8Y"
      },
      "source": [
        "## Извлечение признаков\n",
        "\n",
        "На следующем шаге вы заморозите сверточную базу, созданную на предыдущем шаге, и будете использовать ее в качестве средства извлечения признаков. Кроме того, вы добавите сверху классификатор и обучите классификатор верхнего уровня."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "CnMLieHBCwil"
      },
      "source": [
        "### Заморозка светрочной сети"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "7fL6upiN3ekS"
      },
      "source": [
        "Перед компиляцией и обучением модели важно заморозить сверточную сеть. Замораживание происходит путем установки параметра layer.trainable = False и предотвращает обновление весов в данном слое во время обучения. MobileNet V2 имеет много слоев, поэтому установка флага для всей модели в значение False заморозит их все."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "OTCJH4bphOeo"
      },
      "outputs": [],
      "source": [
        "base_model.trainable = False"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "jsNHwpm7BeVM"
      },
      "source": [
        "### Важное примечание о слоях BatchNormalization\n",
        "\n",
        "Многие модели содержат слои `tf.keras.layers.BatchNormalization`. Этот слой является особым случаем, и следует принимать определенные меры предосторожности в процессе тонкой настройки, как показано далее в этом руководстве.\n",
        "\n",
        "Когда вы устанавливаете `layer.trainable = False`, слой `BatchNormalization` будет работать в режиме вывода и не будет обновлять свою статистику среднего значения и дисперсии.\n",
        "\n",
        "Когда вы размораживаете модель, содержащую слои `BatchNormalization`, чтобы выполнить точную настройку, вы должны держать слои `BatchNormalization` в режиме вывода, передавая `training = False` при вызове базовой модели. В противном случае обновления, применяемые к необучаемым весам, уничтожат то, чему модель научилась.\n",
        "\n",
        "Подробности см. В [Руководстве по переносу обучения](https://www.tensorflow.org/guide/keras/transfer_learning)."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "KpbzSmPkDa-N"
      },
      "outputs": [],
      "source": [
        "# Давайте посмотрим на архитектуру модели\n",
        "base_model.summary()"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "wdMRM8YModbk"
      },
      "source": [
        "### Добавление верхних слоев классификации"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "QBc31c4tMOdH"
      },
      "source": [
        "Чтобы получить прогнозы из блока признаков, выполните усреднение по матрице `5x5`, используя слой `tf.keras.layers.GlobalAveragePooling2D` для преобразования матрицы признаков `5х5х960` в один вектор из 1280 элементов для каждого изображения."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "dLnpMF5KOALm"
      },
      "outputs": [],
      "source": [
        "global_average_layer = tf.keras.layers.GlobalAveragePooling2D()\n",
        "feature_batch_average = global_average_layer(feature_batch)\n",
        "print(feature_batch_average.shape)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "O1p0OJBR6dOT"
      },
      "source": [
        "Apply a `tf.keras.layers.Dense` layer to convert these features into a single prediction per image. You don't need an activation function here because this prediction will be treated as a `logit`, or a raw prediction value.  Positive numbers predict class 1, negative numbers predict class 0.\n",
        "\n",
        "Примените слой `tf.keras.layers.Dense`, чтобы преобразовать эти признаки в один прогноз для изображения. Здесь вам не нужна функция активации, потому что этот прогноз будет рассматриваться как `logit` или необработанное значение прогноза. Положительные числа предсказывают класс 1, отрицательные числа предсказывают класс 0."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "Wv4afXKj6cVa"
      },
      "outputs": [],
      "source": [
        "prediction_layer = tf.keras.layers.Dense(1)\n",
        "prediction_batch = prediction_layer(feature_batch_average)\n",
        "print(prediction_batch.shape)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "HXvz-ZkTa9b3"
      },
      "source": [
        "Постройте модель, объединив вместе слои аугментации, масштабирования, базовую модель и слои извлечения признаков с помощью [Keras Functional API](https://www.tensorflow.org/guide/keras/functional). Как упоминалось ранее, используйте `training = False` для базовой модели, поскольку наша модель содержит слои `BatchNormalization`."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "DgzQX6Veb2WT"
      },
      "outputs": [],
      "source": [
        "inputs = tf.keras.Input(shape=(160, 160, 3))\n",
        "x = data_augmentation(inputs)\n",
        "x = preprocess_input(x)\n",
        "x = base_model(x, training=False)\n",
        "x = global_average_layer(x)\n",
        "x = tf.keras.layers.Dropout(0.2)(x)\n",
        "outputs = prediction_layer(x)\n",
        "model = tf.keras.Model(inputs, outputs)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "g0ylJXE_kRLi"
      },
      "source": [
        "### Компиляция модели\n",
        "\n",
        "Скомпилируйте модель перед ее обучением. Поскольку существует два класса, используйте бинарную кросс-энтропию как фунцию потерь, с парамтром `from_logits = True`, поскольку модель предуматривает линейный вывод."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "RpR8HdyMhukJ"
      },
      "outputs": [],
      "source": [
        "base_learning_rate = 0.0001\n",
        "model.compile(optimizer=tf.keras.optimizers.Adam(lr=base_learning_rate),\n",
        "              loss=tf.keras.losses.BinaryCrossentropy(from_logits=True),\n",
        "              metrics=['accuracy'])"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "I8ARiyMFsgbH"
      },
      "outputs": [],
      "source": [
        "model.summary()"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "lxOcmVr0ydFZ"
      },
      "source": [
        "2.2 млн параметров в MobileNet заморожены, но в полносвязном Dense слое есть 1,2 тыс. _trainable_(тренируемых) параметров. Они распределены между двумя объектами `tf.Variable` - весами и смещениями."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "krvBumovycVA"
      },
      "outputs": [],
      "source": [
        "len(model.trainable_variables)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "RxvgOYTDSWTx"
      },
      "source": [
        "### Обучение модели\n",
        "\n",
        "После обучения в течении 10 эпох, вы должны увидеть точность ~94% на проверочном датасете"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "Om4O3EESkab1"
      },
      "outputs": [],
      "source": [
        "initial_epochs = 10\n",
        "\n",
        "loss0, accuracy0 = model.evaluate(validation_dataset)"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "8cYT1c48CuSd"
      },
      "outputs": [],
      "source": [
        "print(\"Начальная величина потерь: {:.2f}\".format(loss0))\n",
        "print(\"Начальная точность: {:.2f}\".format(accuracy0))"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "JsaRFlZ9B6WK"
      },
      "outputs": [],
      "source": [
        "history = model.fit(train_dataset,\n",
        "                    epochs=initial_epochs,\n",
        "                    validation_data=validation_dataset)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "Hd94CKImf8vi"
      },
      "source": [
        "### График обучения\n",
        "\n",
        "Давайте посмотрим на кривые точности/потери обучения и проверки при использовании базовой модели MobileNet V2 в качестве экстрактора фиксированных признаков."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "53OTCh3jnbwV"
      },
      "outputs": [],
      "source": [
        "acc = history.history['accuracy']\n",
        "val_acc = history.history['val_accuracy']\n",
        "\n",
        "loss = history.history['loss']\n",
        "val_loss = history.history['val_loss']\n",
        "\n",
        "plt.figure(figsize=(8, 8))\n",
        "plt.subplot(2, 1, 1)\n",
        "plt.plot(acc, label='Training Accuracy')\n",
        "plt.plot(val_acc, label='Validation Accuracy')\n",
        "plt.legend(loc='lower right')\n",
        "plt.ylabel('Accuracy')\n",
        "plt.ylim([min(plt.ylim()),1])\n",
        "plt.title('Training and Validation Accuracy')\n",
        "\n",
        "plt.subplot(2, 1, 2)\n",
        "plt.plot(loss, label='Training Loss')\n",
        "plt.plot(val_loss, label='Validation Loss')\n",
        "plt.legend(loc='upper right')\n",
        "plt.ylabel('Cross Entropy')\n",
        "plt.ylim([0,1.0])\n",
        "plt.title('Training and Validation Loss')\n",
        "plt.xlabel('epoch')\n",
        "plt.show()"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "foWMyyUHbc1j"
      },
      "source": [
        "Примечание. Если вам интересно, почему метрики валидации явно лучше, чем метрики обучения, главный фактор заключается в том, что уровни `tf.keras.layers.BatchNormalization` и `tf.keras.layers.Dropout` влияют на точность во время обучения, но отключаются при расчете величины потерь при валидации.\n",
        "\n",
        "В меньшей степени это также связано с тем, что метрики обучения показывают среднее значение за эпоху, тогда как метрики валидации оцениваются после окончания эпохи, поэтому метрики валидации видят модель, которая тренировалась немного дольше."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "CqwV-CRdS6Nv"
      },
      "source": [
        "## Тонкая настройка\n",
        "\n",
        "В эксперименте по извлечению признаков вы тренировали только несколько слоев поверх базовой модели MobileNet V2. Веса предварительно обученной сети **не** обновлялись во время обучения.\n",
        "\n",
        "Один из способов еще больше повысить точность - это обучить(или «тонко настроить») веса верхних слоев предварительно обученной модели вместе с обучением добавленного вами классификатора. В процессе обучения веса должны быть настроены с общих признаков на признаки, связанные с вашим набором данных.\n",
        "\n",
        "Примечание. Это следует делать только после того, как вы обучили классификатор верхнего уровня с предварительно обученной и замороженной базовой моделью. Если вы добавите случайно инициализированный классификатор поверх предварительно обученной модели и попытаетесь обучить все слои вместе, величина обновлений градиента будет слишком большой (из-за случайных весов классификатора), и ваша предварительно обученная модель просто забудет то, чему она предварительно научилась.\n",
        "\n",
        "Кроме того, вам следует попытаться настроить небольшое количество верхних уровней, а не всю модель MobileNet. В большинстве сверточных сетей чем выше уровень, тем он более специализирован. Первые несколько слоев изучают очень простые и общие функции, которые распространяются практически на все типы изображений. По мере того, как вы поднимаетесь выше, функции становятся все более специфичными для набора данных, на котором была обучена модель. Цель точной настройки - адаптировать эти специализированные функции для работы с новым набором данных, а не перезаписывать общее обучение."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "CPXnzUK0QonF"
      },
      "source": [
        "### Разморозка верхних уровней модели\n"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "rfxv_ifotQak"
      },
      "source": [
        "Все, что вам нужно сделать, это разморозить `base_model` и сделать нижние слои недоступными для обучения. Затем вам следует перекомпилировать модель (необходимо, чтобы эти изменения вступили в силу) и возобновить обучение."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "4nzcagVitLQm"
      },
      "outputs": [],
      "source": [
        "base_model.trainable = True"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "-4HgVAacRs5v"
      },
      "outputs": [],
      "source": [
        "# Посмотрим сколько слоев в базовой модели\n",
        "print(\"Количество словев базовой модели: \", len(base_model.layers))\n",
        "\n",
        "# Выполняем точную настройку, начиная с этого слоя.\n",
        "fine_tune_at = 100\n",
        "\n",
        "# Замораживаем все слои до `fine_tune_at`\n",
        "for layer in base_model.layers[:fine_tune_at]:\n",
        "  layer.trainable =  False"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "4Uk1dgsxT0IS"
      },
      "source": [
        "### Компиляция модели\n",
        "\n",
        "Поскольку вы тренируете гораздо большую модель и хотите повторно адаптировать предварительно обученные веса, на этом этапе важно использовать более низкую скорость обучения. В противном случае ваша модель может очень быстро переобучиться. Поэтому уменьшим base_learning_rate в 10 раз"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "NtUnaz0WUDva"
      },
      "outputs": [],
      "source": [
        "model.compile(loss=tf.keras.losses.BinaryCrossentropy(from_logits=True),\n",
        "              optimizer = tf.keras.optimizers.RMSprop(lr=base_learning_rate/10),\n",
        "              metrics=['accuracy'])"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "WwBWy7J2kZvA"
      },
      "outputs": [],
      "source": [
        "model.summary()"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "bNXelbMQtonr"
      },
      "outputs": [],
      "source": [
        "len(model.trainable_variables)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "4G5O4jd6TuAG"
      },
      "source": [
        "### Продолжение обучения модели"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "0foWUN-yDLo_"
      },
      "source": [
        "Если на этапе предыдущего обучения вы получили схождение, то этот шаг повысит вашу точность на несколько процентов. Заметьте, что обучение начинается с эпохи, взятой из истории предыдущего обучения."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "ECQLkAsFTlun"
      },
      "outputs": [],
      "source": [
        "fine_tune_epochs = 10\n",
        "total_epochs =  initial_epochs + fine_tune_epochs\n",
        "\n",
        "history_fine = model.fit(train_dataset,\n",
        "                         epochs=total_epochs,\n",
        "                         initial_epoch=history.epoch[-1],\n",
        "                         validation_data=validation_dataset)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "TfXEmsxQf6eP"
      },
      "source": [
        "Давайте посмотрим на кривые точности/потери обучения и проверки при тонкой настройке последних нескольких уровней базовой модели MobileNet V2 и обучении классификатора поверх нее. \n",
        "Если величина потерь при валидации намного выше, чем при тренировке, значит ваша модель переобучилась.\n",
        "Вы также можете столкнуться с переобучением, поскольку новый обучающий набор относительно невелик и похож на исходные наборы данных MobileNet V2."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "DNtfNZKlInGT"
      },
      "source": [
        "После тонкой настройки модель получает почти 98% точности на проверочном датасете."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "PpA8PlpQKygw"
      },
      "outputs": [],
      "source": [
        "acc += history_fine.history['accuracy']\n",
        "val_acc += history_fine.history['val_accuracy']\n",
        "\n",
        "loss += history_fine.history['loss']\n",
        "val_loss += history_fine.history['val_loss']"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "chW103JUItdk"
      },
      "outputs": [],
      "source": [
        "plt.figure(figsize=(8, 8))\n",
        "plt.subplot(2, 1, 1)\n",
        "plt.plot(acc, label='Training Accuracy')\n",
        "plt.plot(val_acc, label='Validation Accuracy')\n",
        "plt.ylim([0.8, 1])\n",
        "plt.plot([initial_epochs-1,initial_epochs-1],\n",
        "          plt.ylim(), label='Start Fine Tuning')\n",
        "plt.legend(loc='lower right')\n",
        "plt.title('Training and Validation Accuracy')\n",
        "\n",
        "plt.subplot(2, 1, 2)\n",
        "plt.plot(loss, label='Training Loss')\n",
        "plt.plot(val_loss, label='Validation Loss')\n",
        "plt.ylim([0, 1.0])\n",
        "plt.plot([initial_epochs-1,initial_epochs-1],\n",
        "         plt.ylim(), label='Start Fine Tuning')\n",
        "plt.legend(loc='upper right')\n",
        "plt.title('Training and Validation Loss')\n",
        "plt.xlabel('epoch')\n",
        "plt.show()"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "R6cWgjgfrsn5"
      },
      "source": [
        "### Оценка и прогнозирование"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "PSXH7PRMxOi5"
      },
      "source": [
        "Наконец, вы можете проверить точность модели на новых данных с помощью тестового датасета."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "2KyNhagHwfar"
      },
      "outputs": [],
      "source": [
        "loss, accuracy = model.evaluate(test_dataset)\n",
        "print('Точность на тестовых данных :', accuracy)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "8UjS5ukZfOcR"
      },
      "source": [
        "Теперь все готово, чтобы использовать эту модель для предсказаний, будет ли ваш питомец кошкой или собакой."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "RUNoQNgtfNgt"
      },
      "outputs": [],
      "source": [
        "# Получаем пакет данных из тестового датасета\n",
        "image_batch, label_batch = test_dataset.as_numpy_iterator().next()\n",
        "predictions = model.predict_on_batch(image_batch).flatten()\n",
        "\n",
        "# Применяем сигмоиду, поскольку наша модель возвращает logits\n",
        "predictions = tf.nn.sigmoid(predictions)\n",
        "predictions = tf.where(predictions < 0.5, 0, 1)\n",
        "\n",
        "print('Predictions:\\n', predictions.numpy())\n",
        "print('Labels:\\n', label_batch)\n",
        "\n",
        "plt.figure(figsize=(10, 10))\n",
        "for i in range(9):\n",
        "  ax = plt.subplot(3, 3, i + 1)\n",
        "  plt.imshow(image_batch[i].astype(\"uint8\"))\n",
        "  plt.title(class_names[predictions[i]])\n",
        "  plt.axis(\"off\")"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "_TZTwG7nhm0C"
      },
      "source": [
        "## Заключение\n",
        "\n",
        "* **Использование предварительно обученной модели для извлечения признаков**: при работе с небольшим набором данных обычной практикой является использование возможностей модели, обученной на более крупном наборе данных в том же домене. Это делается путем создания экземпляра предварительно обученной модели и добавления полносвязного классификатора(Dense) сверху. Предварительно обученная модель «заморожена», и во время обучения обновляются только веса классификатора.\n",
        "В этом случае сверточная сеть извлекает все признаки, связанные с каждым изображением, и вы обучаете классификатор, который определяет класс изображения с учетом этого набора извлеченных признаков.\n",
        "\n",
        "* **Тонкая настройка предварительно обученной модели**: для дальнейшего повышения производительности можно переназначить верхние уровни предварительно обученных моделей для нового набора данных с помощью тонкой настройки.\n",
        "В этом случае вы настраиваете свои веса так, чтобы ваша модель изучала признаки в верхних слоях, в то время, как остальные слои остаются замороженные. Этот метод обычно рекомендуется, когда обучающий набор данных большой и очень похож на исходный набор данных, на котором была обучена предварительно обученная модель.\n",
        "\n",
        "Чтобы узнать больше, посетите [Руководство по переносу обучения](https://www.tensorflow.org/guide/keras/transfer_learning).\n"
      ]
    }
  ],
  "metadata": {
    "accelerator": "GPU",
    "colab": {
      "collapsed_sections": [],
      "name": "transfer_learning.ipynb",
      "toc_visible": true
    },
    "kernelspec": {
      "display_name": "Python 3",
      "name": "python3"
    }
  },
  "nbformat": 4,
  "nbformat_minor": 0
}
